﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Chambills.Present.Core.NotifyOnChange
{
    /// <summary>
    /// A class that implements <see cref="INotifyPropertyChanged" /> and can act as a base class for any class
    /// that wishes to implement change notification.
    /// </summary>
    public class NotifiableObject : ManagedDisposable, INotifyPropertyChanged
    {
        static NotifiableObject()
        {
            
        }

        public NotifiableObject()
        {
            PropertyChanged += (sender, e) => { };
        }


        

        /// <summary>
        /// Fires the <see cref="PropertyChanged"/> event for the provided property of the class.
        /// Uses Expressions to avoid magic strings
        ///<example>OnPropertyChanged(()=>this.Property);</example>
        /// </summary>
        protected void OnPropertyChanged<T>(Expression<Func<T>> property)
        {
            OnPropertyChanged(((MemberExpression)property.Body).Member.Name);
        }


        protected void OnPropertyChanged(string propertyName)
        {
            AssertPropertyIsOnThisType(propertyName);
            onPropertyChanged(propertyName);
            RaiseDependentProperties(propertyName);
        }

        private void onPropertyChanged(string propertyName)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }


        /// <summary>
        /// All properties that have specified <see cref="DependsOnAttribute" /> with this propertyName will be fired.
        /// </summary>
        /// <param name="propertyName"></param>
        private void RaiseDependentProperties(string propertyName)
        {
            TypeCache.Instance[this.GetType()][propertyName]
                .DependentProperties
                .ForEach(p => OnPropertyChanged(p.Name));
        }


        [Conditional("DEBUG")]
        private void AssertPropertyIsOnThisType(string propertyName)
        {
            Debug.Assert(GetProperty(propertyName) != null);
        }

        private PropertyInfo GetProperty(string propertyName)
        {
            return this.GetType().GetProperties().FirstOrDefault(property => property.Name == propertyName);
        }

        public event PropertyChangedEventHandler PropertyChanged;

    }
}